repo/commit: Add support for --selinux-policy-from-base
authorColin Walters <walters@verbum.org>
Sat, 21 Mar 2020 14:48:24 +0000 (14:48 +0000)
committerColin Walters <walters@verbum.org>
Tue, 24 Mar 2020 16:34:26 +0000 (16:34 +0000)
The [dev-overlay](https://github.com/coreos/coreos-assembler/blob/332c6ab3b91778d904224c3c960d9cc4739d60bd/src/cmd-dev-overlay)
script shipped in coreos-assembler mostly exists to deal
with the nontrivial logic around SELinux policy.  Let's make
the use case of "commit some binaries overlaying a base tree, using
the base's selinux policy" just require a magical
`--selinux-policy-from-base` argument to `ostree commit`.

A new C API was added to implement this in the case of `--tree=ref`;
when the base directory is already checked out, we can just reuse
the existing logic that `--selinux-policy` was using.

Requires: https://github.com/ostreedev/ostree/pull/2039

apidoc/ostree-sections.txt
src/libostree/libostree-devel.sym
src/libostree/ostree-repo-commit.c
src/libostree/ostree-repo-private.h
src/libostree/ostree-repo.h
src/ostree/ot-builtin-commit.c
tests/kola/destructive/itest-label-selinux.sh

index 32cf5228a50d1e9d4fae4ede77e610166b6554c1..3525d9f28832af620d48fe87076174e49287d2ca 100644 (file)
@@ -379,6 +379,7 @@ ostree_repo_commit_modifier_new
 OstreeRepoCommitModifierXattrCallback
 ostree_repo_commit_modifier_set_xattr_callback
 ostree_repo_commit_modifier_set_sepolicy
+ostree_repo_commit_modifier_set_sepolicy_from_commit
 ostree_repo_commit_modifier_set_devino_cache
 ostree_repo_commit_modifier_ref
 ostree_repo_commit_modifier_unref
index aa3392ccd9dff9debecdde626fbb3192885f2b6b..3d5fd3bcdf1d5f7d19a0aa95b3c0965367955db1 100644 (file)
@@ -20,7 +20,7 @@
 /* Add new symbols here.  Release commits should copy this section into -released.sym. */
 LIBOSTREE_2020.2 {
 global:
-  someostree_symbol_deleteme;
+  ostree_repo_commit_modifier_set_sepolicy_from_commit;
 } LIBOSTREE_2020.1;
 
 /* Stub section for the stable release *after* this development one; don't
index dac4573ce9fce1cbc9c09c9903204655e9dc4404..3f2f2bebcd13188b5abe86762e572d5c134376cf 100644 (file)
@@ -4227,9 +4227,11 @@ ostree_repo_commit_modifier_unref (OstreeRepoCommitModifier *modifier)
   if (modifier->xattr_destroy)
     modifier->xattr_destroy (modifier->xattr_user_data);
 
-  g_clear_object (&modifier->sepolicy);
   g_clear_pointer (&modifier->devino_cache, (GDestroyNotify)g_hash_table_unref);
 
+  g_clear_object (&modifier->sepolicy);
+  (void) glnx_tmpdir_delete (&modifier->sepolicy_tmpdir, NULL, NULL);
+
   g_free (modifier);
   return;
 }
@@ -4279,6 +4281,60 @@ ostree_repo_commit_modifier_set_sepolicy (OstreeRepoCommitModifier
   modifier->sepolicy = sepolicy ? g_object_ref (sepolicy) : NULL;
 }
 
+/**
+ * ostree_repo_commit_modifier_set_sepolicy_from_commit:
+ * @modifier: Commit modifier
+ * @repo: OSTree repo containing @rev
+ * @rev: Find SELinux policy from this base commit
+ * @cancellable:
+ * @error:
+ *
+ * In many cases, one wants to create a "derived" commit from base commit.
+ * SELinux policy labels are part of that base commit.  This API allows
+ * one to easily set up SELinux labeling from a base commit.
+ */
+gboolean 
+ostree_repo_commit_modifier_set_sepolicy_from_commit (OstreeRepoCommitModifier              *modifier,
+                                                      OstreeRepo                            *repo,
+                                                      const char                            *rev,
+                                                      GCancellable                          *cancellable,
+                                                      GError                               **error)
+{
+  GLNX_AUTO_PREFIX_ERROR ("setting sepolicy from commit", error);
+  g_autofree char *commit = NULL;
+  g_autoptr(GFile) root = NULL;
+  if (!ostree_repo_read_commit (repo, rev, &root, &commit, cancellable, error))
+    return FALSE;
+  const char policypath[] = "usr/etc/selinux";
+  g_autoptr(GFile) policyroot = g_file_get_child (root, policypath);
+  if (!g_file_query_exists (policyroot, NULL))
+    return TRUE;  /* No policy, nothing to do */
+
+  GLnxTmpDir tmpdir = {0,};
+  if (!glnx_mkdtemp ("ostree-commit-sepolicy-XXXXXX", 0700, &tmpdir, error))
+    return FALSE;
+  if (!glnx_shutil_mkdir_p_at (tmpdir.fd, "usr/etc", 0755, cancellable, error))
+    return FALSE;
+
+  OstreeRepoCheckoutAtOptions coopts = {0,};
+  coopts.mode = OSTREE_REPO_CHECKOUT_MODE_USER;
+  coopts.subpath = glnx_strjoina ("/", policypath);
+
+  if (!ostree_repo_checkout_at (repo, &coopts, tmpdir.fd, policypath, commit, cancellable, error))
+    return glnx_prefix_error (error, "policy checkout");
+
+  g_autoptr(OstreeSePolicy) policy = ostree_sepolicy_new_at (tmpdir.fd, cancellable, error);
+  if (!policy)
+    return glnx_prefix_error (error, "reading policy");
+
+  ostree_repo_commit_modifier_set_sepolicy (modifier, policy);
+  /* Transfer ownership */
+  modifier->sepolicy_tmpdir = tmpdir;
+  tmpdir.initialized = FALSE;
+
+  return TRUE;
+}
+
 /**
  * ostree_repo_commit_modifier_set_devino_cache:
  * @modifier: Modifier
index 571cab6b8b4fe298a998120362e6f4f5d69e4b29..9f722a3945aad8f825c5c7e3130a56cd1f66f8c0 100644 (file)
@@ -76,6 +76,7 @@ struct OstreeRepoCommitModifier {
   GDestroyNotify xattr_destroy;
   gpointer xattr_user_data;
 
+  GLnxTmpDir sepolicy_tmpdir;
   OstreeSePolicy *sepolicy;
   GHashTable *devino_cache;
 };
index 40d3f7737984cc54324d47cbda68a240bf31390f..014afff90547779ef0d2e2a1926c8b8156148e19 100644 (file)
@@ -691,6 +691,13 @@ _OSTREE_PUBLIC
 void ostree_repo_commit_modifier_set_sepolicy (OstreeRepoCommitModifier              *modifier,
                                                OstreeSePolicy                        *sepolicy);
 
+_OSTREE_PUBLIC
+gboolean ostree_repo_commit_modifier_set_sepolicy_from_commit (OstreeRepoCommitModifier              *modifier,
+                                                               OstreeRepo                            *repo,
+                                                               const char                            *commit,
+                                                               GCancellable                          *cancellable,
+                                                               GError                               **error);
+
 _OSTREE_PUBLIC
 void ostree_repo_commit_modifier_set_devino_cache (OstreeRepoCommitModifier              *modifier,
                                                    OstreeRepoDevInoCache                 *cache);
index 16b07cb9dd975d47783b38c49526b6360187ae86..4cca56d0268b3de4949aa39e13c30937836736b1 100644 (file)
@@ -53,6 +53,7 @@ static gboolean opt_tar_autocreate_parents;
 static char *opt_tar_pathname_filter;
 static gboolean opt_no_xattrs;
 static char *opt_selinux_policy;
+static gboolean opt_selinux_policy_from_base;
 static gboolean opt_canonical_permissions;
 static gboolean opt_consume;
 static gboolean opt_devino_canonical;
@@ -107,6 +108,7 @@ static GOptionEntry options[] = {
   { "canonical-permissions", 0, 0, G_OPTION_ARG_NONE, &opt_canonical_permissions, "Canonicalize permissions in the same way bare-user does for hardlinked files", NULL },
   { "no-xattrs", 0, 0, G_OPTION_ARG_NONE, &opt_no_xattrs, "Do not import extended attributes", NULL },
   { "selinux-policy", 0, 0, G_OPTION_ARG_FILENAME, &opt_selinux_policy, "Set SELinux labels based on policy in root filesystem PATH (may be /)", "PATH" },
+  { "selinux-policy-from-base", 'P', 0, G_OPTION_ARG_NONE, &opt_selinux_policy_from_base, "Set SELinux labels based on first --tree argument", NULL },
   { "link-checkout-speedup", 0, 0, G_OPTION_ARG_NONE, &opt_link_checkout_speedup, "Optimize for commits of trees composed of hardlinks into the repository", NULL },
   { "devino-canonical", 'I', 0, G_OPTION_ARG_NONE, &opt_devino_canonical, "Assume hardlinked objects are unmodified.  Implies --link-checkout-speedup", NULL },
   { "tar-autocreate-parents", 0, 0, G_OPTION_ARG_NONE, &opt_tar_autocreate_parents, "When loading tar archives, automatically create parent directories as needed", NULL },
@@ -550,6 +552,11 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio
     flags |= OSTREE_REPO_COMMIT_MODIFIER_FLAGS_GENERATE_SIZES;
   if (opt_disable_fsync)
     ostree_repo_set_disable_fsync (repo, TRUE);
+  if (opt_selinux_policy && opt_selinux_policy_from_base)
+    {
+      glnx_throw (error, "Cannot specify both --selinux-policy and --selinux-policy-from-base");
+      goto out;
+    }
 
   if (flags != 0
       || opt_owner_uid >= 0
@@ -557,25 +564,13 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio
       || opt_statoverride_file != NULL
       || opt_skiplist_file != NULL
       || opt_no_xattrs
-      || opt_selinux_policy)
+      || opt_selinux_policy
+      || opt_selinux_policy_from_base)
     {
       filter_data.mode_adds = mode_adds;
       filter_data.skip_list = skip_list;
       modifier = ostree_repo_commit_modifier_new (flags, commit_filter,
                                                   &filter_data, NULL);
-      if (opt_selinux_policy)
-        {
-          glnx_autofd int rootfs_dfd = -1;
-          if (!glnx_opendirat (AT_FDCWD, opt_selinux_policy, TRUE, &rootfs_dfd, error))
-            {
-              g_prefix_error (error, "selinux-policy: ");
-              goto out;
-            }
-          policy = ostree_sepolicy_new_at (rootfs_dfd, cancellable, error);
-          if (!policy)
-            goto out;
-          ostree_repo_commit_modifier_set_sepolicy (modifier, policy);
-        }
     }
 
   if (opt_editor)
@@ -621,6 +616,7 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio
   g_assert (opt_trees && *opt_trees);
   for (tree_iter = (const char *const*)opt_trees; *tree_iter; tree_iter++)
     {
+      const gboolean first = (tree_iter == (const char *const*)opt_trees);
       tree = *tree_iter;
 
       eq = strchr (tree, '=');
@@ -637,12 +633,33 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio
       g_clear_object (&object_to_commit);
       if (strcmp (tree_type, "dir") == 0)
         {
+          if (first && opt_selinux_policy_from_base)
+            {
+              opt_selinux_policy = g_strdup (tree);
+              opt_selinux_policy_from_base = FALSE;
+            }
+          if (first && opt_selinux_policy)
+            {
+              g_assert (modifier);
+              glnx_autofd int rootfs_dfd = -1;
+              if (!glnx_opendirat (AT_FDCWD, opt_selinux_policy, TRUE, &rootfs_dfd, error))
+                goto out;
+              policy = ostree_sepolicy_new_at (rootfs_dfd, cancellable, error);
+              if (!policy)
+                goto out;
+              ostree_repo_commit_modifier_set_sepolicy (modifier, policy);
+            }
           if (!ostree_repo_write_dfd_to_mtree (repo, AT_FDCWD, tree, mtree, modifier,
                                                 cancellable, error))
             goto out;
         }
       else if (strcmp (tree_type, "tar") == 0)
         {
+          if (first && opt_selinux_policy_from_base)
+            {
+              glnx_throw (error, "Cannot use --selinux-policy-from-base with tar");
+              goto out;
+            }
           if (!opt_tar_pathname_filter)
             {
               if (strcmp (tree, "-") == 0)
@@ -707,6 +724,12 @@ ostree_builtin_commit (int argc, char **argv, OstreeCommandInvocation *invocatio
         }
       else if (strcmp (tree_type, "ref") == 0)
         {
+          if (first && opt_selinux_policy_from_base)
+            {
+              g_assert (modifier);
+              if (!ostree_repo_commit_modifier_set_sepolicy_from_commit (modifier, repo, tree, cancellable, error))
+                goto out;
+            }
           if (!ostree_repo_read_commit (repo, tree, &object_to_commit, NULL, cancellable, error))
             goto out;
 
index 7bfd23517512be001b3e5c3354b06235563f635e..d7337124b5ad54aa192cce705064d8f246dfc477 100755 (executable)
@@ -6,6 +6,7 @@ set -xeuo pipefail
 
 . ${KOLA_EXT_DATA}/libinsttest.sh
 require_writable_sysroot
+prepare_tmpdir /var/tmp
 
 date
 cd /ostree/repo/tmp
@@ -87,3 +88,19 @@ rm co -rf
 ostree refs --delete testbranch
 echo "ok checkout selinux and skip-list"
 date
+
+mkdir -p usr/{bin,lib,etc}
+echo 'somebinary' > usr/bin/somebinary
+ls -Z usr/bin/somebinary > lsz.txt
+assert_not_file_has_content lsz.txt ':bin_t:'
+rm -f lsz.txt
+echo 'somelib' > usr/lib/somelib.so
+echo 'someconf' > usr/etc/some.conf
+ostree commit -b newbase --selinux-policy-from-base --tree=ref=${host_refspec} --tree=dir=$(pwd)
+ostree ls -X newbase /usr/bin/somebinary > newls.txt
+assert_file_has_content newls.txt ':bin_t:'
+ostree ls -X newbase /usr/lib/somelib.so > newls.txt
+assert_file_has_content newls.txt ':lib_t:'
+ostree ls -X newbase /usr/etc/some.conf > newls.txt
+assert_file_has_content newls.txt ':etc_t:'
+echo "ok commit --selinux-policy-from-base"